﻿using System;
using System.Windows;
using System.Windows.Interop;

namespace Lds.Ward.PhotoDirectory.WpfApp {
	/// <summary>
	/// Watches the clipboard for changes in content.
	/// </summary>
	public class ClipboardWatcher : IDisposable {
		WindowInteropHelper interopHelper;
		Window window;
		HwndSource hwndSource;
		IntPtr nextClipboardViewer;
		bool clipboardHandlerRegistered;

		public ClipboardWatcher(Window window) {
			if (window == null) {
				throw new ArgumentNullException("window");
			}
			this.window = window;
			this.interopHelper = new WindowInteropHelper(window);
			if (this.interopHelper.Handle != IntPtr.Zero) {
				beginWatchClipboard();
			} else {
				this.window.Activated += window_Activated;
			}
		}
		void window_Activated(object sender, EventArgs e) {
			if (interopHelper.Handle != IntPtr.Zero) {
				hwndSource = HwndSource.FromHwnd(interopHelper.Handle);
				window.Activated -= window_Activated;
				beginWatchClipboard();
			}
		}

		void beginWatchClipboard() {
			if (hwndSource == null) {
				throw new InvalidOperationException(Strings.WindowHasNoHandle);
			}
			hwndSource.AddHook(wndProc);
			nextClipboardViewer = WinAPI.SetClipboardViewer(interopHelper.Handle);
			clipboardHandlerRegistered = true;
		}
		void endWatchClipboard() {
			if (clipboardHandlerRegistered) {
				WinAPI.ChangeClipboardChain(interopHelper.Handle, nextClipboardViewer);
				hwndSource.RemoveHook(wndProc);
				clipboardHandlerRegistered = false;
			}
		}

		IntPtr wndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
			switch ((WinAPI.WindowsMessages)msg) {
				case WinAPI.WindowsMessages.WM_DRAWCLIPBOARD:
					OnClipboardChanged();
					// Forward the message down the chain
					WinAPI.SendMessage(nextClipboardViewer, (WinAPI.WindowsMessages)msg, wParam, lParam);
					handled = true;
					break;
				case WinAPI.WindowsMessages.WM_CHANGECBCHAIN:
					if (wParam == nextClipboardViewer)
						nextClipboardViewer = lParam;
					else
						WinAPI.SendMessage(nextClipboardViewer, (WinAPI.WindowsMessages)msg, wParam, lParam);
					handled = true;
					break;
			}
			return IntPtr.Zero;
		}

		#region Eventing
		/// <summary>
		/// The event that fires when the clipboard contents change.
		/// </summary>
		public static RoutedEvent ClipboardChangedEvent =
			EventManager.RegisterRoutedEvent("ClipboardChanged", RoutingStrategy.Bubble,
			typeof(RoutedEventHandler), typeof(ClipboardWatcher));
		/// <summary>
		/// The event that fires when the clipboard contents change.
		/// </summary>
		public event RoutedEventHandler ClipboardChanged {
			add { window.AddHandler(ClipboardChangedEvent, value); }
			remove { window.RemoveHandler(ClipboardChangedEvent, value); }
		}
		/// <summary>
		/// Fires the ClipboardChanged event.
		/// </summary>
		protected virtual void OnClipboardChanged() {
			window.RaiseEvent(new RoutedEventArgs(ClipboardChangedEvent, this));
		}

		#endregion
		#region IDisposable Members

		protected virtual void Dispose(bool disposing) {
			if (disposing) {
				// dispose of managed objects
			}
			// dispose of native handles
			endWatchClipboard();
		}
		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		~ClipboardWatcher() {
			Dispose(false);
		}

		#endregion
	}
}
